#lang racket
(require racket/block)
(provide replace
         invoke
         exec)

(define (lambda-symbol? symbol)
  (equal? symbol
          'lambda))

(define (lambda-fun? expr)
  (and (pair? expr)
       (lambda-symbol? (first expr))
       (list? (second expr))))

; [List of Symbol] [List of Expression] -> Lambda-fun
(define (make-lf args body)
  (append (list 'lambda
                args)
          body))

(define (lf-formal-params lambda-fun)
  (second lambda-fun))

(define (lf-body lambda-fun)
  (rest (rest lambda-fun)))

(define (call-fun expr)
  (first expr))

(define (call-args expr)
  (rest expr))

(define (call? expr)
  (and (pair? expr)
       (not (lambda-fun? expr))
       (or (symbol?     (call-fun expr))
           (lambda-fun? (call-fun expr))
           (call?       (call-fun expr)))))

(define (contains? lst obj)
  (cond [(empty? lst)
         #f]
        [(equal? (first lst)
                 obj)
         #t]
        [else
         (contains? (rest lst)
                    obj)]))

(define (replace expr from to)

  (define (replace-help expr) ; Определим вспомогательную функцию
    (replace expr from to))   ; Она должна принимать только одно выражение.
 ; Эта функция локальная, при каждом вызове replace она будет использовать
 ; Новые from и to, те, которые были передан в replace в этот раз.

  (cond [(symbol? expr)
         (if (equal? expr from) ; Если expr - это переменная, которую надо заменить
             to ; Мы её заменяем, вернув новое значение
             expr)] ; В противном случае, ничего с ней не делаем.

        [(lambda-fun? expr)
         (if (contains? (lf-formal-params expr)
                        from); По нашим правилам, если у функции есть такой-же параметр
             expr ; Мы ничего не торгаем.
             (make-lf (lf-formal-params expr) ; В противном случае
                      (map replace-help ; Заменяем все вхождения данной переменной
                           (lf-body expr))))]  ; В тело лямбда-фукции.

        [(call? expr) ; если наше выражение - это вызов функции
         (map replace-help ; мы просто заменяем переменную на её значение
              expr)] ; во всех выражениях, входящих в вызов.
        [else
         expr]))

(define (invoke fun args)
  (define formal-params (lf-formal-params fun))
  (define body          (lf-body          fun))

  (cond [(not (lambda-fun? fun))
         (error 'not-a-function "~A - это не функция." fun)]
        [(and (empty? args)
              (empty? formal-params))
         (evaluate (cons 'begin body))]

        [(and (not (empty? args))
              (empty? formal-params))
         (error 'more-arguments "Аргументов слишком много: ~A~%~%~A" fun args)]

        [(and (empty? args)
              (not (empty? formal-params)))
         fun]

        [(and (not (empty? args))
              (not (empty? formal-params)))
         (block
          (define var (first formal-params))
          (define arg (first args))
          (define (replace-var-to-arg expr)
            (replace expr var arg))

          (invoke (cons 'lambda
                        (cons (rest formal-params)
                              (map replace-var-to-arg
                                   body)))
                  (rest args)))]
        [else
         (error "Кашмар! что-то вообще не то с invoke")]))

(define (evaluate expr)
  (cond [(lambda-fun? expr)
         expr]
        [(call? expr)
         (block
          (define operator (evaluate  (call-fun expr)))
          (define args     (call-args expr))
          (match operator
            ['begin
             (if (empty? (rest args))
                 (evaluate (first args))
                 (begin
                   (evaluate (first args))
                   (evaluate (cons 'begin
                                   (rest args)))))]
            [lambda-fun?
             (invoke operator
                     args)]
            [_ (error 'bad-operator "Плохой оператор ~A" operator)]))]
        [else
         expr]))
